package com.gn.compile;

import com.gn.domain.Phrase;

import java.io.*;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;

public class PL0 {
    public static StringBuffer errorBuffer = new StringBuffer(); //#新增存放报错信息
    public static final int al = 10;			// 符号的最大长度
    public static final int amax = 2047;		// 最大允许的数值
    public static final int cxmax = 500;		// 最多的虚拟机代码数
    public static final int levmax = 3;			// 最大允许过程嵌套声明层数 [0, levmax]
    public static final int nmax = 14;			// number的最大位数
    public static final int norw = 32;			// 关键字个数
    public static final int txmax = 100;		// 名字表容量
    public static PrintStream fa;				// 输出虚拟机代码
    public static PrintStream fa1;				// 输出源文件及其各行对应的首地址
    public static PrintStream fa2;				// 输出结果
    public static PrintStream fas;				// 输出名字表
    //下面两个标志位，不进行询问了，直接输出
    public static boolean listswitch = true;			// 显示虚拟机代码与否
    public static boolean tableswitch = true;			// 显示名字表与否
    public static Scanner lex;					// 词法分析器
    public static Parser  parser;				// 语法分析器
    public static Interpreter interp;			// 类P-Code解释器（及目标代码生成工具）
    public static Table table;					// 名字表

    // 为避免多次创建BufferedReader，我们使用全局统一的Reader
    // BufferedReader是读取控制台输入流的缓冲读取器
    public static BufferedReader stdin;			// 标准输入

    public PL0(BufferedReader fin) {
        lex = new Scanner(fin);
    }

    /**
     * 新添加
     * PL0构造函数重载，接受String类型的源代码输入
     */
    public PL0(String code) {
        //#词法分析器
        lex = new Scanner(code);

        table = new Table();
        interp = new Interpreter();
        parser = new Parser(lex, table, interp);
    }


    /**
     * 执行编译动作
     * @return 是否编译成功
     */
    boolean compile() {
        boolean abort = false;//#是否终止
        lex.reset();
        errorBuffer.setLength(0);
        try {
            //#输出虚拟机代码的文件fa
            PL0.fa = new PrintStream("fa.tmp");
            //#输出名字表的文件fas
            PL0.fas = new PrintStream("fas.tmp");
            parser.nextSym();		// 前瞻分析需要预先读入一个符号
            parser.parse();			// 开始语法分析过程（连同语法检查、目标代码生成）
        } catch (Error e) {
            // 如果是发生严重错误则直接中止
            abort = true;
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally { //#无论是否发生异常都会执行，保证文件输出流能够正常关闭，缓存内容能够写入文件。
            PL0.fa.close();
            PL0.fa1.close();//#这个输出源文件及其各行对应的首地址的文件是在main函数中打开的。
            PL0.fas.close();
            //#关闭错误提示文件
            //ErrorReader.close();  //前端一直运行，不能关！
        }
        if (abort)
            System.exit(0);//#终止标志abort为True，则退出程序，终止代码0。

        // 编译成功是指完成编译过程并且没有错误
        return (Err.err == 0);
    }

    public void executeScanner() {
        String operator; // 存放是什么运算符
        lex.getsym();
        while (lex.sym != Symbol.period) {
            if (lex.sym == Symbol.ident) { //标识符
                //System.out.println("标识符" + lex.id + "\t(1,'" + lex.id + "')");
                System.out.printf("%-18s%-12s\n", "标识符" + lex.id, "(1,'" + lex.id + "')");
            }
            else if (lex.sym == Symbol.number) { //常数
                //System.out.println("常数" + lex.num + "\t(2,'" + lex.num + "')");
                System.out.printf("%-18s%-12s\n", "常数" + lex.num, "(2,'" + lex.num + "')");
            }
            else if (lex.sym.ordinal() > Symbol.becomes.ordinal() && lex.sym.ordinal() <= Symbol.procsym.ordinal()) {    // 关键字
                //System.out.println("关键字" + lex.id + "\t(3,'" + lex.id + "')");
                System.out.printf("%-18s%-12s\n", "关键字" + lex.id, "(3,'" + lex.id + "')");
            }
            else if (lex.sym.ordinal() > Symbol.number.ordinal() && lex.sym.ordinal() <= Symbol.geq.ordinal() ||
                    lex.sym == Symbol.becomes) {  //运算符
                switch (lex.sym) {
                    case becomes:
                        operator = ":=";
                        break;
                    case leq:
                        operator = "<=";
                        break;
                    case lss:
                        operator = "<";
                        break;
                    case geq:
                        operator = ">=";
                        break;
                    case gtr:
                        operator = ">";
                        break;
                    default:
                        operator = Character.toString(lex.preChar);
                }
                //System.out.println("运算符" + operator + "\t(4,'" + operator + "')");
                System.out.printf("%-18s%-12s\n", "运算符" + operator, "(4,'" + operator + "')");
            }
            else if (lex.sym.ordinal()>Symbol.geq.ordinal() && lex.sym.ordinal() <= Symbol.period.ordinal()) {  //界符
                //System.out.println("界符" + lex.preChar + "\t(5,'" + lex.preChar + "')");
                System.out.printf("%-18s%-12s\n","界符" + lex.preChar,"(5,'" + lex.preChar + "')");
            }
            lex.getsym();
        }
        //System.out.println("界符" + '.' + "\t(5,'" + '.' + "')"); //打印最后的程序结束符
        System.out.printf("%-18s%-12s\n", "界符" + '.', "(5,'" + '.' + "')");
        //关闭fa1文件
        fa1.close();
    }

    public String executeScannerOneByOne() {
        StringBuilder out = new StringBuilder();
        Formatter fmt = new Formatter(out);
        String operator;
        lex.getsym();

        if (lex.sym == Symbol.ident) {  //标识符
            fmt.format("%-18s%-12s\n", "标识符" + lex.id, "(1,'" + lex.id + "')");
        }
        else if (lex.sym == Symbol.number) {//常数
            fmt.format("%-18s%-12s\n", "常数" + lex.num, "(2,'" + lex.num + "')");
        }
        else if (lex.sym.ordinal() > Symbol.becomes.ordinal() && lex.sym.ordinal() <= Symbol.procsym.ordinal()) {  // 关键字
            fmt.format("%-18s%-12s\n", "关键字" + lex.id, "(3,'" + lex.id + "')");
        }
        else if (lex.sym.ordinal() > Symbol.number.ordinal() && lex.sym.ordinal() <= Symbol.geq.ordinal() ||
                lex.sym == Symbol.becomes) { //运算符
            switch (lex.sym) {
                case becomes:
                    operator = ":=";
                    break;
                case leq:
                    operator = "<=";
                    break;
                case lss:
                    operator = "<";
                    break;
                case geq:
                    operator = ">=";
                    break;
                case gtr:
                    operator = ">";
                    break;
                default:
                    operator = Character.toString(lex.preChar);
            }
            fmt.format("%-18s%-12s\n", "运算符" + operator, "(4,'" + operator + "')");
        }
        else if (lex.sym.ordinal() > Symbol.geq.ordinal() && lex.sym.ordinal() <= Symbol.period.ordinal()) {//界符
            fmt.format("%-18s%-12s\n", "界符" + lex.preChar, "(5,'" + lex.preChar + "')");
        }
        return out.toString();
    }

    public static PL0 getSource(BufferedReader fin) {
        PL0 pl0 = null;
        try {
            //#fa1输出源文件及其各行对应的首地址
            //#创建文件输出流，输出到根目录的fa1.tmp文件中
            PL0.fa1 = new PrintStream("fa1.tmp");
            // 构造编译器并初始化
            pl0 = new PL0(fin);
            //# 静态成员变量一个类的所有对象共用一个，所以新new出来的编译器对象部分属性已经设置完成。
            //# 构造函数需要文件缓存阅读器fin，调用构造函数创建lex(词法分析器),parser(语法分析器),interp(类P-code解释器)对象
        } catch (IOException e) {
            e.printStackTrace();
        }
        return pl0;
    }

    //接受String类型的源代码输入的getSource重载
    public static PL0 getSource(String code) {
        PL0 pl0 = null;
        try {
            stdin = new BufferedReader(new InputStreamReader(System.in));//#创建读取控制台输入流的缓存读取器
            //#fa1输出源文件及其各行对应的首地址
            //#创建文件输出流，输出到根目录的fa1.tmp文件中
            PL0.fa1 = new PrintStream("fa1.tmp");

            // 构造编译器并初始化
            pl0 = new PL0(code);
            //# 静态成员变量一个类的所有对象共用一个，所以新new出来的编译器对象部分属性已经设置完成。
            //# 构造函数需要文件缓存阅读器fin，调用构造函数创建lex(词法分析器),parser(语法分析器),interp(类P-code解释器)对象
        } catch (IOException e) {
            e.printStackTrace();
        }
        return pl0;
    }

    /**
     * 新增，编译运行
     */
    public void compileAndInterpret()
    {
        try
        {
            if (compile())
            {
                // 如果成功编译则接着解释运行
                //#输出结果的文件fa2.tmp
                PL0.fa2 = new PrintStream("fa2.tmp");
                interp.interpret();//#解释执行，输出结果到文件和控制台。
                PL0.fa2.close();
            } else
            {//#如果没有编译通过
                System.out.print("Errors in pl/0 program");
            }
        }catch (IOException e)
        {
            e.printStackTrace();
        }
    }
    public List<Instruction> executeCompile1()
    {
        compile();
        List<Instruction> instructions = new ArrayList<>();
        for (Instruction instruction : interp.code)
        {
            if(instruction != null)
            {
                instructions.add(instruction);
            }
        }
        return instructions;
    }

    public List<Phrase> executeCompile2()
    {
        compile();
        List<Instruction> instructions = new ArrayList<>();
        for (Instruction instruction : interp.code)
        {
            if(instruction != null)
            {
                instructions.add(instruction);
            }
            else
            {
                break;
            }
        }
        List<Phrase> phraseList = new ArrayList<>();
        for (Instruction i : instructions)
        {
            Phrase phrase = new Phrase();
            phrase.setL(i.l + "");
            phrase.setA(i.a + "");
            phrase.setF(i.f + "");
            phraseList.add(phrase);
        }
        return phraseList;
    }

    public List<Phrase> executeCompile()
    {
        compile();
        List<Phrase> phraseList = new ArrayList<>();
        for (Instruction instruction : interp.code)
        {
            if(instruction != null)
            {
                Phrase phrase = new Phrase();
                phrase.setF(String.valueOf(instruction.f));
                phrase.setL(String.valueOf(instruction.l));
                phrase.setA(String.valueOf(instruction.a));
                phraseList.add(phrase);
            }
            else
            {
                break;
            }
        }
        if (PL0.errorBuffer.length() == 0)
        {
            PL0.errorBuffer.append("编译成功！");
        }
        Phrase ph = new Phrase();
        //phraseList中最后一个元素的第一个String的位置永远是编译器输出信息
        ph.setF(PL0.errorBuffer.toString());
        ph.setL("");
        ph.setA("");
        phraseList.add(ph);
        return phraseList;
    }

    public void printCodeList()
    {
        List<Phrase> phraseList = new ArrayList<>();
        for (Instruction instruction : interp.code)
        {
            if(instruction != null)
            {
                Phrase phrase = new Phrase();
                phrase.setF(String.valueOf(instruction.f));
                phrase.setL(String.valueOf(instruction.l));
                phrase.setA(String.valueOf(instruction.a));
                phraseList.add(phrase);
            }
            else
            {
                break;
            }
        }
        System.out.println(phraseList);
    }

    //为了与原程序兼容，此主文件依然保留，运行main文件中的main函数
    public static void main(String[] args) {
        String fname;
        stdin = new BufferedReader(new InputStreamReader(System.in));//#创建读取控制台输入流的缓存读取器
        BufferedReader fin;//#声明读取输入PL/0文件的缓冲读取器
        try {
            // 输入文件名
            fname = "../../Examples/Sample0.pl0";//#便于调试
            System.out.print("Input pl/0 file?   ");
            while (fname.equals("")) { //#直到fname不是空，否则一直读取
                fname = stdin.readLine();//#通过缓存读取器一读读一行
            }
            fin = new BufferedReader(new FileReader(fname), 4096);//#创建对象，将文件读入缓存，缓存区4kb

            // 是否输出虚拟机代码
            fname = "y";//#将存储文件路径和文件名的变量清空，之后用于存储读取到的y/n
            System.out.print("List object code?(Y/N)");
            while (fname.equals("")) { //#逻辑同读取文件路径
                fname = stdin.readLine();
            }
            //#如果读取到的一行中第一个字符为y或者是Y则设置显示虚拟机代码开关为True，这里赋值号右边返回的是逻辑值，
            //#直接赋值给这个类中的公有静态成员变量，不写"compile.PL0."也没有问题
            //#并且，基本数据类型的成员变量有默认值，boolean类型的默认值为false
            PL0.listswitch = (fname.charAt(0)=='y' || fname.charAt(0)=='Y');

            // 是否输出名字表
            fname = "y";//#清空fname，读取用户输入的内容使用的是同一个变量，减少内存开销。
            System.out.print("List symbol table?(Y/N)");
            while (fname.equals("")) {
                fname = stdin.readLine();
            }
            //#设置显示名字表开关
            PL0.tableswitch = (fname.charAt(0)=='y' || fname.charAt(0)=='Y');

            //#fa1输出源文件及其各行对应的首地址
            //#创建文件输出流，输出到根目录的fa1.tmp文件中
            PL0.fa1 = new PrintStream("fa1.tmp");
            PL0.fa1.println("Input pl/0 file?   " + fname);//#首先输出了要进行编译的文件路径和文件名。

            // 构造编译器并初始化
            PL0 pl0 = new PL0(fin);//#现在才构造编译器对象，之前设置对象中的属性的时设置的都是静态成员变量的属性，
            //# 静态成员变量一个类的所有对象共用一个，所以新new出来的编译器对象部分属性已经设置完成。
            //# 构造函数需要文件缓存阅读器fin，调用构造函数创建lex(词法分析器),parser(语法分析器),interp(类P-code解释器)对象

            //调用词法分析程序
            //pl0.executeScanner();
            System.out.println("逐条输出：");
            while (lex.sym != Symbol.period) {
                System.out.print(pl0.executeScannerOneByOne());
            }

        } catch (IOException e) {
            //#catch到IOException即文件打开失败
            System.out.println("Can't open file!");
        }
        System.out.println();
    }
}
